﻿using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;

using Kannon.Core;

namespace Kannon.Server
{
    class Program
    {
        private static string workingDirectory;
        private static string baseDirectory;
        private static string assemblyName;
        private static AppDomain serverDomain;
        private static IKannonApplicationServer kannonAppServer;
        private static DateTime? reloadAt;

        static void Main(string[] args)
        {
            Console.WriteLine("Kannon Server");
            Console.WriteLine();

            if(args.Length != 1)
            {
                Console.WriteLine("Usage: ks.exe <assembly>");
                return;
            } // if

            var assemblyPath = args[0];
            assemblyName = Path.GetFileName(assemblyPath);

            //
            // <assembly> may contain either file name, or a relative path. Either way,
            // we need to find out path to where all the files are located
            baseDirectory =
                Path.GetDirectoryName(assemblyPath) == "" ?
                    Environment.CurrentDirectory :
                    Path.GetDirectoryName(Path.GetFullPath(assemblyPath));

            workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
            Console.WriteLine("Using '{0}' as a working directory and '{1}' as assembly name", workingDirectory, assemblyName);

            //
            // We want to have automatic code reloading. To do so, first
            // copy all files from current working directory to a temp location
            // and establish FileSystemWatcher to watch it
            ShadowCopyAssemblies(baseDirectory, workingDirectory);

            StartServer(workingDirectory, assemblyName);

            var watcher = new FileSystemWatcher(baseDirectory) { NotifyFilter = NotifyFilters.LastWrite };
            watcher.Changed += AssembliesChanged;
            
            watcher.EnableRaisingEvents = true;

            var reloader = new Thread(RestartServer) { IsBackground = true };
            reloader.Start();

            do
            {
                
                var key = Console.ReadKey(true);
                if(key.Modifiers == ConsoleModifiers.Control && key.Key == ConsoleKey.X) break;
            } while(true);

            Console.WriteLine("Stopping server");
        }

        private static void StartServer(string workingDirectory, string assemblyName)
        {
            Console.WriteLine("Starting server...");

            var tkas = typeof(HttpListenerKannonApplicationServer);
            
            serverDomain = AppDomain.CreateDomain(workingDirectory + "-" + assemblyName);
            kannonAppServer = (IKannonApplicationServer)serverDomain.CreateInstanceAndUnwrap(tkas.Assembly.FullName, tkas.FullName);

            var asmPath = Path.Combine(workingDirectory, assemblyName);

            kannonAppServer.HostAssembly(asmPath);
            kannonAppServer.Start();

            Console.WriteLine("Loaded assemblies:");

            foreach(var a in AppDomain.CurrentDomain.GetAssemblies())
                Console.WriteLine("\t" + a.FullName);
        }

        

        private static void ShadowCopyAssemblies(string baseDirectory, string workingDirectory)
        {
            new DirectoryInfo(baseDirectory).CopyAll(new DirectoryInfo(workingDirectory),
                (s, d) => Console.WriteLine("\tShadow-copied '{0}'", d));
        }

        private static void AssembliesChanged(object sender, FileSystemEventArgs e)
        {
            //Console.WriteLine("Detected changes in '{0}' ({1}). Reloading in 2 seconds", e.FullPath, e.ChangeType);
            reloadAt = DateTime.Now.AddSeconds(2);

            //RestartServer();
        }

        private static void RestartServer()
        {
            do
            {
                if(reloadAt == null)
                {
                    //Thread.Sleep(TimeSpan.FromSeconds(1));
                    //Console.WriteLine("No reloading required");
                    continue;
                }

                var now = DateTime.Now;
                if(reloadAt.Value > now)
                {
                    var ts = reloadAt.Value - now;
                    Console.WriteLine("Reloading in " + ts);
                    Thread.Sleep(ts);
                    continue;
                } // if

                reloadAt = null;

                kannonAppServer.Stop();
                kannonAppServer = null;
                AppDomain.Unload(serverDomain);
                serverDomain = null;

                if(!string.IsNullOrEmpty(workingDirectory))
                {
                    Directory.Delete(workingDirectory, true);
                    Console.WriteLine("Deleted '{0}'", workingDirectory);
                } // if

                workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

                ShadowCopyAssemblies(baseDirectory, workingDirectory);

                

                StartServer(workingDirectory, assemblyName);
            } 
            while(true);
        }
    }

    [Serializable]
    public class Runner
    {
        private readonly string workingDirectory;
        private readonly string assemblyName;

        public Runner(string workingDirectory, string assemblyName)
        {
            this.workingDirectory = workingDirectory;
            this.assemblyName = assemblyName;
        }

        public void ServerRunner()
        {
            try
            {
                var asmPath = Path.Combine(workingDirectory, assemblyName);
                var asm = Assembly.LoadFrom(asmPath);

                var t = (from et in asm.GetExportedTypes() where typeof(KannonApplication).IsAssignableFrom(et) select et).FirstOrDefault();

                Console.WriteLine("Running " + t.FullName);

                var server = new HttpListenerKannonApplicationServer();
                server.Start();
            }
            catch(Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }}

    public static class DirectoryInfoExtensions
    {
        public static void CopyAll(this DirectoryInfo source, DirectoryInfo destination, Action<string, string> fileCopyCallback)
        {
            try
            {
                if(!destination.Exists)
                    destination.Create();

                foreach(var file in source.GetFiles())
                {
                    var destinationFileName = Path.Combine(destination.ToString(), file.Name);
                    file.CopyTo(destinationFileName, true);

                    if(fileCopyCallback != null)
                        fileCopyCallback.Invoke(file.FullName, destinationFileName);
                } // foreach

                foreach(var directory in source.GetDirectories())
                {
                    var nextDestination = destination.CreateSubdirectory(directory.Name);
                    CopyAll(directory, nextDestination, fileCopyCallback);
                } // foreach
            } // try
            catch(IOException ie)
            {
            } // catch
        }
    }

    public static class Sandbox
    {
        public static void RunInAppDomain(Action delg, AppDomain targetDomain)
        {
            domainDomainRunner runner = new domainDomainRunner(delg);         
            targetDomain.DoCallBack(new CrossAppDomainDelegate(runner.Invoke));
        }

        public static void RunInAppDomain(Action delg, params object[] args)
        {           
            AppDomain tempDomain = AppDomain.CreateDomain("domain_RunInAppDomain" + delg.GetHashCode());
            try
            {
                    RunInAppDomain(delg, tempDomain, args);
            }
            finally
            {
                    AppDomain.Unload(tempDomain);
            }
        }

        [Serializable]
        internal class domainDomainRunner
        {
            private System.Delegate _delegate;
            public domainDomainRunner(System.Delegate delg)
            {
                    _delegate=delg;
            }
            public void Invoke()
            {
                _delegate.DynamicInvoke();
            }
        }
    }
}
